package controleur;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;

import vue.Observeur;
import modele.Amelioration;
import modele.Direction;
import modele.Joueur;
import modele.Partie;
import modele.Position;
import modele.Batiment.Batiment;
import modele.Batiment.BatimentDefensif;
import modele.Batiment.ExtracteurOr;
import modele.Batiment.ExtracteurElixir;
import modele.Batiment.CampMilitaire;
import modele.Batiment.HotelDeVille;
import modele.Batiment.Laboratoire;
import modele.Unite.Ouvrier;
import modele.Unite.Unite;
import modele.Unite.UniteAttaquante;

public class Controleur implements Control{
	Partie partie;
	Observeur observeur;
	
	public static int tempsDAttenteMillisecondesConstruction = 3000;
	public static int tempsDAttenteMillisecondesDeplacement = 1000;
	public static int tempsDAttenteMillisecondesExtractionOr = 1000;
	public static int tempsDAttenteMillisecondesExtractionElixir = 1000;
	
	/**
	 * retourne la liste des bâtiments du joueur
	 * @return liste des bâtiments
	 */
	public ArrayList<Batiment> getListeBatiment()
	{
		return partie.getJoueur().getListeBatiment();
	}


	public Controleur(Observeur obs)
	{
		this.partie = new Partie();
		observeur = obs;
		
		initialiserBatiment();
		initialiserOuvrier();
		//test du changement de coordonnees
		new testChangementCoordonnees();
		new MonTimer(new TacheControlerQuetesEtRafraichir(this), TacheControlerQuetesEtRafraichir.nbrMillisecondesAvantRepetition);
	}
	
	public Controleur(Observeur obs, Partie maPartie)
	{
		this.partie = maPartie;
		observeur = obs;
		
		initialiserBatiment();
		initialiserOuvrier();
		//test du changement de coordonnees
		new testChangementCoordonnees();
		new MonTimer(new TacheControlerQuetesEtRafraichir(this), TacheControlerQuetesEtRafraichir.nbrMillisecondesAvantRepetition);
	}
	
	
	/**
	 * teste le bon fonctionnement des fonctions PositionModeleToPositionVue et PositionVueToPositionModele
	 * @author thomas
	 *
	 */
	private class testChangementCoordonnees
	{
		public testChangementCoordonnees()
		{
			Position pos = new Position(4,4);
			Position tmp;
			tmp = PositionModeleToPositionVue(pos);
			tmp = PositionVueToPositionModele(tmp);
			//asssert tmp.equals(pos);
			//if(tmp.equals(pos) != true)
			//{
			//	System.out.println("pos = "+pos.getL()+","+pos.getC());
			//	System.out.println("tmp = "+tmp.getL()+","+tmp.getC());
			//	System.out.println("DEBUG : erreur dans le passage des coordonnées du modèle à la vue et de la vue au modèle");
			//}
		}
	}
	
	/**
	 * crée les bâtiments avant le lancement de la partie
	 */
	private void initialiserBatiment()
	{
		//on crée l'hotel de ville
		Batiment hotelDeVille = new HotelDeVille(new Position(4,4));
		hotelDeVille.setJoueur(this.getJoueur());
		partie.getQuetes().get(partie.getNumeroQueteActuelle()).getMap().marquerCreationBatiment(hotelDeVille);
		partie.getJoueur().ajouterBatiment(hotelDeVille);
	}
	
	
	/**
	 * crée les bâtiments avant le lancement de la partie
	 */
	private void initialiserOuvrier()
	{
		//on crée l'ouvrier
		Ouvrier ouvrier = new Ouvrier(new Position(4,3), Direction.BAS, this.getJoueur());
		partie.getJoueur().ajouterOuvrier(ouvrier);
	}

	
	
	/**
	 * visible depuis la vue
	 * construit un bâtiment dans le modèle
	 * @param x abscisse dans la matrice
	 * @param y ordonnee dans la matrice
	 */
	public void construireBatiment(Batiment batiment)
	{
		if(partie.getMap().positionValide(batiment.getPosition()) && partie.getMap().estLibre(batiment.getPosition()))
		{
			if(partie.getJoueur().payerBatiment(batiment) == 1)
			{
				Ouvrier constructeur = partie.getJoueur().getConstructeur();
				if(constructeur != null)
				{
					batiment.setJoueur(partie.getJoueur());
					constructeur.construireBatiment(this, partie.getJoueur(), batiment, partie.getQuetes().get(partie.getNumeroQueteActuelle()).getMap());
					
					//partie.getJoueur().creerBatiment(this, batiment, partie.getQuetes().get(partie.getNumeroQueteActuelle()).getMap());
				}
			}
			//batiment.actionApresConstruction(this);
			this.observeur.rafraichir();
		}
	}
	
	
	@Override
	//on ne l'utilise pas encore dans la vue
	public Position PositionModeleToPositionVue(Position pos)
	{
		return new Position((int) (25.0+pos.getC()*(30.0)+pos.getL()*(300.0)/9.0), (int) (220.0+pos.getC()*(-25.0)+pos.getL()*(25.0)));
	}
	
	@Override
	public Position PositionVueToPositionModele(Position pos)
	{
		double x = pos.getL();
		double y = pos.getC();
		return new Position( (int)Math.round((3.0/190.0)*x + (9.0/475.0)*y - (867.0/190.0) ), (int)  Math.round((3.0/190.0)*x - (2.0/95.0)*y + (161.0/38.0)) );
	}

	/**
	 * permet de controler l'avancement des quêtes
	 */
	public void controlerAvancementQuetes()
	{
		//si la quete actuelle est realise alors queteActuelle = queteSuivante
		//si on a fini la quete alors on passe à la quête suivante
		if(partie.getNumeroQueteActuelle() < partie.getQuetes().size())
		{
			if(partie.getQuetes().get(partie.getNumeroQueteActuelle()).estFinie(partie.getJoueur()))
			{
				partie.setNumeroQueteActuelle(partie.getNumeroQueteActuelle()+1);
			}
		}
	}

	@Override
	public ArrayList<Batiment> recupererListeBatiment() {
		return getListeBatiment();
	}

	@Override
	public ArrayList<Ouvrier> recupererListeOuvrier() {
		return partie.getJoueur().getListeOuvrier();
	}
	
	@Override
	public void ameliorerBatiment(Batiment batiment)
	{
		//partie.getJoueur().getBatimentSelectionne().augmenterNiveau();
		batiment.augmenterNiveau();
	}
	
	
	
	/*@Override
	public void selectionnerBatiment(int x, int y)
	{
		Position pos = new Position(x,y);
		
		boolean batimentTrouve = false;
		for(int i = 0; (i < partie.getJoueur().getListeBatiment().size() && batimentTrouve == false); i++)
		{
			Batiment batiment = partie.getJoueur().getListeBatiment().get(i);
			if(batiment.getPosition().equals(pos))
			{
				partie.getJoueur().setBatimentSelectionne(batiment);	//on met à jour le batiment sélectionné
				batimentTrouve = true;
			}
		}
	}*/


	@Override
	public void creerUniteAttaquante(CampMilitaire camp, UniteAttaquante uniteAttaquante) {
		uniteAttaquante.setCamp(camp);
		if(camp.uniteAttaquanteCreable(uniteAttaquante))
		camp.creerUniteAttaquante(partie.getJoueur(), uniteAttaquante);
	}


	

	@Override
	public ArrayList<UniteAttaquante> recupererListeUniteAttaquante() {
		return partie.getJoueur().getListeUniteAttaquante();
	}
	
	@Override
	public int recupererOrJoueur() {
		return partie.getJoueur().getRessourceOr();
	}


	@Override
	public int recupererElixirJoueur() {
		return partie.getJoueur().getRessourceElixir();
	}
	
	@Override
	public void sauvegarderPartie(String nomSauvegarde)
	{
		try{
			// ouverture d'un flux de sortie vers le fichier "personne.serial"
			FileOutputStream fos = new FileOutputStream(nomSauvegarde);
	
			// création d'un "flux objet" avec le flux fichier
			ObjectOutputStream oos= new ObjectOutputStream(fos);
			try {
				// sérialisation : écriture de l'objet dans le flux de sortie
				oos.writeObject(partie); 
				// on vide le tampon
				oos.flush();
			} finally {
				//fermeture des flux
				try {
					oos.close();
				} finally {
					fos.close();
				}
			}
		}
		catch(IOException ioe) {
			//ioe.printStackTrace();
			//si la sauvegarde échoue on ne fait rien
		}
	}
	
	
	public int chargerPartie(String nomSauvegarde)
	{
		try {
			// ouverture d'un flux d'entrée depuis le fichier "personne.serial"
			FileInputStream fis = new FileInputStream(nomSauvegarde);
			// création d'un "flux objet" avec le flux fichier
			ObjectInputStream ois= new ObjectInputStream(fis);
			try {	
				// désérialisation : lecture de l'objet depuis le flux d'entrée
				partie = (Partie) ois.readObject(); 
			} finally {
				// on ferme les flux
				try {
					ois.close();
				} finally {
					fis.close();
				}
			}
		} catch(IOException ioe) {
			//ioe.printStackTrace();
			return 0;
		} catch(ClassNotFoundException cnfe) {
			//cnfe.printStackTrace();
			return 0;
		}
		if(partie.getEtatPartie() == 1)
		{
			this.lancerAssaut();
		}
		else
		{
			for(int i = 0; i < this.getListeBatiment().size(); i++)//pour chaque extracteur on relance les timers
			{
				if(getListeBatiment().get(i).getClass().getName().equals("modele.Batiment.ExtracteurOr"))
				{
					new MonTimer(new TacheExtractionOr(this, partie.getJoueur(), (ExtracteurOr)getListeBatiment().get(i)), Controleur.tempsDAttenteMillisecondesExtractionOr);
				}
				else if(getListeBatiment().get(i).getClass().getName().equals("modele.Batiment.Elixir"))
				{
					new MonTimer(new TacheExtractionElixir(this, partie.getJoueur(), (ExtracteurElixir)getListeBatiment().get(i)), Controleur.tempsDAttenteMillisecondesExtractionElixir);
				}
			}
		}
		return 1;
	}
	
	
	
	
	public void provoquerRafraichissement()
	{
		observeur.rafraichir();
	}
	
	public Joueur getJoueur()
	{
		return partie.getJoueur();
	}
	
	
	@Override
	public int getNumeroQueteActuelle() {
		return partie.getNumeroQueteActuelle();
	}
	
	/////////////////////////////////////////////////////////////
	//		Partie référant au développement des améliorations
	/////////////////////////////////////////////////////////////
	
	
	/**
	 * augmente le niveau de l'amélioration AmeliorationPuissanceAttaque
	 */
	public void augmenterNiveauAmeliorationPuissanceAttaque(Laboratoire labo)
	{
		labo.getAmeliorations().get(0).augmenterNiveau(partie.getJoueur());
	}
	
	/////////////////////////////////////////////////////////////
	//		Partie référant à l'attaque de l'ennemi
	/////////////////////////////////////////////////////////////
	@Override
	public ArrayList<Batiment> recupererListeBatimentEnnemi()
	{
		return partie.getListeBatimentJoueurEnnemi();
	}
	
	@Override
	public void attaquerEnnemi() {
		partie.attaquer();
		
	}
	
	@Override
	public void finirAttaqueEnnemi() {
		partie.finirAttaque();
		
	}
	
	@Override
	public boolean EtreEnTrainDAttaquer()
	{
		if(partie.getEtatPartie() == 1)
			return true;
		else
			return false;
	}
	
	@Override
	public void placerUniteAttaquante(Unite unite) {
		if(partie.getQuetes().get(partie.getNumeroQueteActuelle()).getMap().positionValide(unite.getPosition()))
		{
			UniteAttaquante uniteAttaquante = null;
			
			for(int i=0; i<partie.getJoueur().getListeUniteAttaquante().size();i++)
			{
				if(partie.getJoueur().getListeUniteAttaquante().get(i).getClass().getName().equals(unite.getClass().getName()))
				{
					uniteAttaquante = partie.getJoueur().getListeUniteAttaquante().get(i);
					break;
				}
			}
			uniteAttaquante.setPosition(unite.getPosition());
			//on supprime l'unité attaquante de son camp militaire
			uniteAttaquante.getCamp().getListeUniteAttaquante().remove(uniteAttaquante);
			partie.getJoueur().getListeUniteAttaquante().remove(uniteAttaquante);
			uniteAttaquante.setJoueur(partie.getJoueur());
			partie.unitesAttaquantes.add(uniteAttaquante);
			
			lancerAssaut();
		}
	}
	
	/**
	 * ne fonctionne que pour les unites attaquant au corps à corps
	 * @param controleur controleur qui s'occupe des timers
	 */
	public void calculerListesDirectionsEtLancerTimer()
	{
		Batiment batimentAAttaquer = this.recupererListeBatimentEnnemi().get(0);
		//pour toutes les unites attaquantes
		//	trouver le chemin vers un batiment à attaquer (le plus proche)
		//	lancer le timer d'attaque
		for(int i = 0; i < partie.unitesAttaquantes.size(); i++)
		{
			recupererListeUniteAttaquanteEngagees().get(i).setPositionArrivee(new Position(3,3));
			//ici il faut calculer le nouveau chemin
			recupererListeUniteAttaquanteEngagees().get(i).setListeDirection(partie.getMap().chercherCheminSansDerniereCase(recupererListeUniteAttaquanteEngagees().get(i).getPosition(), new Position(3,3), 20));
			recupererListeUniteAttaquanteEngagees().get(i).seDeplacer(partie.getMapAdverse());
			//ici il faut lancer le timer d'attaque
			new MonTimer(new TacheDeplacementEtAttaqueCorps(this, recupererListeUniteAttaquanteEngagees().get(i).getClockArmeBaissee(), recupererListeUniteAttaquanteEngagees().get(i), partie.getMapAdverse(), batimentAAttaquer),Controleur.tempsDAttenteMillisecondesDeplacement);
		}
		
		/*
		//version 2
		
		//Batiment batimentAAttaquer = this.recupererListeBatimentEnnemi().get(0);
		//pour toutes les unites attaquantes
		//	trouver le chemin vers un batiment à attaquer (le plus proche)
		//	lancer le timer d'attaque
		for(int i = 0; i < recupererListeUniteAttaquante().size(); i++)
		{
			assert recupererListeBatimentEnnemi().size() > 0;
			UniteAttaquante actuelle = recupererListeUniteAttaquante().get(i);
			Batiment cible = recupererListeBatimentEnnemi().get(0);
			int distanceBatimentCible = Map.distance(new Case(actuelle.getPosition().getC(), actuelle.getPosition().getL()), new Case(cible.getPosition().getC(), cible.getPosition().getL()));
			for(int j = 1; j < this.recupererListeBatimentEnnemi().size(); j++)
			{
				if(Map.distance(new Case(actuelle.getPosition().getC(), actuelle.getPosition().getL()), new Case(recupererListeBatimentEnnemi().get(j).getPosition().getC(), recupererListeBatimentEnnemi().get(j).getPosition().getL())) > distanceBatimentCible)
				{
					cible = recupererListeBatimentEnnemi().get(j);
					distanceBatimentCible = Map.distance(new Case(actuelle.getPosition().getC(), actuelle.getPosition().getL()), new Case(recupererListeBatimentEnnemi().get(j).getPosition().getC(), recupererListeBatimentEnnemi().get(j).getPosition().getL()));
				}
			}
			partie.getJoueur().getListeUniteAttaquante().get(i).setPositionArrivee(cible.getPosition());
			partie.getJoueur().getListeUniteAttaquante().get(i).seDeplacer(partie.getMap());
			//ici il faut lancer le timer d'attaque
			new MonTimer(new TacheDeplacementEtAttaqueCorps(this, Controleur.tempsDAttenteMillisecondesDeplacement, partie.getJoueur().getListeUniteAttaquante().get(i), partie.getMap(), cible),Controleur.tempsDAttenteMillisecondesDeplacement);
		}
		*/
	}
	
	/**
	 * lance l'assaut une fois que les unités attaquantes sont placées
	 */
	public void lancerAssaut()
	{
		calculerListesDirectionsEtLancerTimer();
		lancerTimerBatimentDefensif();
	}
	
	/**
	 * lance les timers pour la defense
	 */
	private void lancerTimerBatimentDefensif() {
		for(int i= 0; i < this.recupererListeBatimentEnnemi().size(); i++)
		{
			if(recupererListeBatimentEnnemi().get(i).getClass().getName().equals("modele.Batiment.Canon")
				|| recupererListeBatimentEnnemi().get(i).getClass().getName().equals("modele.Batiment.Mortier")
				|| recupererListeBatimentEnnemi().get(i).getClass().getName().equals("modele.Batiment.TourArcher")
				|| recupererListeBatimentEnnemi().get(i).getClass().getName().equals("modele.Batiment.TourSorcier"))
			new MonTimer(new TacheDefendre(this, (BatimentDefensif)recupererListeBatimentEnnemi().get(i)), TacheDefendre.nbrMillisecondesAvantRepetition);
		}
	}


	@Override
	public ArrayList<UniteAttaquante> recupererListeUniteAttaquanteEngagees() {
		return partie.unitesAttaquantes;
	}


	
}
